#include "BattleControl.h"

// -- Sub systems --
#include "../engine/EngineController.h"
#include "../gfx/RenderEngine.h"
#include "../shared/Time.h"
#include "../engine/input.h"
#include "../net/NetworkClient.h"

// -- Objects --
#include "../engine/Gamestate.h"
#include "../engine/Unit.h"
#include "../engine/StaticObject.h"
#include "../engine/Map.h"
#include "../gfx/RenderableUnit.h"
#include "../engine/RenderableUnitImp.h"
#include "../phy/WorldLink.h"
#include "../engine/Bullet.h"

// -- Utils --
#include "../shared/macros.h"
#include "../shared/utils.h"
#include "../shared/shared_types.h" // vec3f
#include "../shared/logger.h"
#include "../utils/Profiler.h"

// -- STL --
#include <algorithm>
#include <sstream>
#include <iostream>
namespace Putin{

BattleControl::BattleControl()
{
	mGame = NULL;
	mPhycccp = NULL;
	mAIManager = NULL;
	mFrameCounter = 0;
	mFrameCounter16 = 0;
	mNetworkMode = false;
	mMap = NULL;
	mNet = NULL;
	mWaitForSync = false;
	mUpdateEvent = NULL;
	mWorldLink = NULL;
}

BattleControl::~BattleControl()
{
	SAFE_DELETE(mGame);
	SAFE_DELETE(mPhycccp);
	SAFE_DELETE(mAIManager);
	SAFE_DELETE(mMap);
	SAFE_DELETE(mNet);
	// do not delete mWorldLink, its in fact controlled by mPhycccp
}

bool BattleControl::initialize()
{

	mAIManager = new AIManager(mGame);


	return true;
}

void BattleControl::terminate()
{
	SAFE_DELETE(mPhycccp);
}

bool BattleControl::quit()
{
	// quit program??
	EngineController::instance().quit();
	return true;
}

bool BattleControl::update()
{
	double elapsedTime = Time::instance().getLastFrameTime();

	// handle Input
	if( InputManager::instance().isKeyDown(VK_ESCAPE))
	{
		quit();
	}

	// --- Sync things ---
	if( isMultiplay())
	{
		mNet->update();
	}

	// Run in single play or if we are not syncing this frame
	if(!isMultiplay() || ( isMultiplay() && mNet->getState() == NetworkClient::RUNNING && !mWaitForSync))
	{
	
		// Update physics
		startProfile("mPhycccp->Update");
		mPhycccp->Update();
		stopProfile("mPhycccp->Update");

		// Update AI
		startProfile("mAIManager->update");
		mAIManager->update(elapsedTime);
		stopProfile("mAIManager->update");
	}


	if( isMultiplay() && mNet->getState() == NetworkClient::RUNNING)
	{

		this->mFrameCounter ++;
		this->mFrameCounter16 ++;

		// check if this is a key frame
		if( mFrameCounter16 % KEYFRAME_INTERVAL == 0)
		{
			// Sync!!!!!!!
			mNet->send( &NetworkCommand(NC_FRAME,mFrameCounter,0,0,0));
			mWaitForSync = true;

			// Key frame.
			NetworkClient::CommandQueue& cq = mNet->getCommands();
			while(!cq.empty())
			{
				// execute command
				NetworkCommand nc = cq.front();
				cq.pop();
				executeCommand(&nc);
			}
		}
	}

	if( mUpdateEvent )
		mUpdateEvent->frameEvent(this);
	

	return true;
}

bool BattleControl::render()
{
	bool ret = renderGraphics();
	ret = ret && renderOverlay();
	return ret;
}

bool BattleControl::renderOverlay()
{
	return true;
}

bool BattleControl::renderGraphics()
{
	// just copy the Renderables container to a vector
	/*std::vector<RenderableUnit*> rVec;
	rVec.reserve( mRenderables.size());
	std::transform(mRenderables.begin(), mRenderables.end(), std::back_inserter(rVec), second(mRenderables));*/

	startProfile("updateUnits");
	RenderEngine::instance().updateUnits(mRenderables);
	stopProfile("updateUnits");
	startProfile("renderOneFrame");
	RenderEngine::instance().renderOneFrame();
	stopProfile("renderOneFrame");

	// clear some stuff TES TEST TEST
	for each( std::pair<int,Unit*> up in mGame->mUnits)
	{
		up.second->mEffectList.clear(); // TODO: Remove, should not be used
		up.second->clearEffectQueues();
	}
	
	while( !mNotInitedUnits.empty())
	{
		Unit* u = mNotInitedUnits.front();
		mNotInitedUnits.pop();
		mPhycccp->AddDynamicObject(u,u->getID());
	}
	while( !mNotInitedStatics.empty())
	{
		StaticObject* obj = mNotInitedStatics.front();
		mNotInitedStatics.pop();
		mPhycccp->AddStaticObject(obj,obj->getID());
	}

	return true;
}

bool BattleControl::loadResources()
{
	// Should be overriden, perhaps?
	if( isMultiplay())
	{
		// Create the network connection
		mNet = new NetworkClient();
	}

		// Init Physics
	mWorldLink = new WorldLink(RenderEngine::instance().mSceneManager);
	mPhycccp = new PhysicsHandler(mWorldLink);
	
	return true;
}

bool BattleControl::loadMap(std::string filename)
{
	mMap = new Map();
	mMap->loadFromFile(filename);
	return true;
}

bool BattleControl::spawnUnit(Unit* u)
{
	// Assign an id.
	// TODO: if threaded, a lock should be here
	
	int id = mGame->nextId();
	u->mId = id;
	if(!mGame->add(u))
	{
		// Something is wrong with id. 
		// TODO: handle!
		Logger::instance().loggLine("duplicated ID number detected: " + Utils::intToString(id) );
		return false;
	}

	// Notify all subsystems about the new unit
	createRenderable(u); // set it up for rendering..

	mNotInitedUnits.push(u); // Setup for physics addition later on

	return true;
}

void BattleControl::removeUnit(int id)
{
	mGame->removeUnit(id);

	// Remove from rendering
	for(std::vector<RenderableUnit*>::iterator it = mRenderables.begin(); 
		it != mRenderables.end(); it++)
	{
		if( atoi((*it)->getId().c_str()) == id )
		{
			//mRenderables.erase(it);
			removeRenderable(id);
			mPhycccp->RemoveDynamicObject(id);
			// TODO: remove from AI
			break;
		}
	} 
}


bool BattleControl::spawnStatic(Putin::StaticObject* obj)
{
	// Assign an id.
	// TODO: if threaded, a lock should be here
	
	int id = mGame->nextId();
	obj->mId = id;
	mStatics[id] = obj;
	
	// Notify all subsystems about the new unit
	createRenderable(obj); // set it up for rendering..

	// Set up for physics (cannot be done here, since the unit is not yet rendered)
	mNotInitedStatics.push(obj);

	return true;
}


void BattleControl::removeStatic(int id)
{
	// TODO: REMOVE STATIC UNITS NOT NEEDED!!!!!
	removeRenderable(id);
	// remove from update queues as well.
}

void BattleControl::spawnBullet(Bullet* b)
{
	int id = mGame->nextId();
	b->mId = id;
	mBullets[id] = b;
	createRenderable(b);
	mPhycccp->AddProjectileObject(b);
}

void BattleControl::removeBullet(Bullet* b)
{
	int id = b->getID();
	if( mBullets[id] != NULL)
	{
		// Remove from renderer
		removeRenderable( b->getID());
		// TODO: Remove from physics
		
		// Remove
		SAFE_DELETE(b);
		mBullets.erase(id);
	}
}

Bullet* BattleControl::getBullet(int id)
{
	return mBullets[id];
}

void BattleControl::removeRenderable(int id)
{
	std::vector<RenderableUnit*>::iterator it = mRenderables.begin();
	for(;it != mRenderables.end();it++)
	{
		RenderEngine::instance().removeUnit(*it);  // remove from Ogre and destroy nodes
		//SAFE_DELETE(*it);						   // delete RenderableUnit object
		mRenderables.erase(it);					   // remove from vector
		break;
	}
}


Gamestate* BattleControl::getState()
{
	return mGame;
}

void BattleControl::mouseDown(int x, int y, int mb)
{
	// normalize mouse points
	Vec2f np = normalizeScreenCoord( Vec2f(x,y));

	/*std::stringstream ss;
	ss << "Point: (" << x << ","<< y << ") - ("<<np.x << "," << np.y << ")";
	MessageBoxA(NULL,ss.str().c_str(),"",NULL);*/
	// Ok, make a selection
	int unitID = RenderEngine::instance().renderableUnitQuery( np );		
	Vec3f wp = RenderEngine::instance().worldPositionQuery(np);
	
	if( mb == VK_LBUTTON || mb == VK_RBUTTON || mb == VK_MBUTTON)
	{
		if( unitID > 0 )
			unitClicked(unitID, wp, mb);
		else if( !wp.isInf() ) // check that we did not click skybox or whatever
			groundClicked(wp,mb);
	}
}

void BattleControl::mouseUp(int x, int y, int mb)
{
	
}

void BattleControl::mouseMove(int x, int y)
{
	
}

void BattleControl::keyDown(char key)
{
	
}

void BattleControl::keyUp(char key)
{
	
}

void BattleControl::keyPress(char c)
{

}
void BattleControl::unitClicked(int unitID, Vec3f& pos, int mouse )
{
	std::stringstream ss;
	std::string m = mouse == VK_LBUTTON ? "left" : ( mouse == VK_RBUTTON ? "right" : "middle");
	ss << "Unit clicked at (" << pos.x << ","<<pos.y<<","<<pos.z<<"), id: "<< unitID<< ", with "<<m<<" mouse button";
	MessageBoxA(RenderEngine::instance().getWinHandle(), ss.str().c_str(),"Ground clicked",MB_ICONINFORMATION);
}

void BattleControl::groundClicked(Vec3f& pos, int mouse)
{
	std::stringstream ss;
	std::string m = mouse == VK_LBUTTON ? "left" : ( mouse == VK_RBUTTON ? "right" : "middle");
	ss << "Ground clicked at (" << pos.x << ","<<pos.y<<","<<pos.z<<") with "<<m<<" mouse button";
	MessageBoxA(RenderEngine::instance().getWinHandle(), ss.str().c_str(),"Ground clicked",MB_ICONINFORMATION);
}

void BattleControl::executeCommand(NetworkCommand* nc)
{
	// Check for Commands left over from game implementation
	if( nc->command == NC_FRAME )
	{
		// we have sync.
		mWaitForSync  = false;
	}
}

void BattleControl::executeText(NetworkText* nt)
{
	
}

// Utility functions

RenderableUnit* BattleControl::createRenderable(GameObject *go)
{
	int id = go->getID();
	RenderableUnit* ru = new RenderableUnitImpl(go);
	mRenderables.push_back(ru);
	return ru;
}

Vec2f BattleControl::normalizeScreenCoord(Vec2f c)
{
	// assume c is in screen space coords
	// Calculate and return the 0-1 normalized coords
	// depending on the width and height of the render window
	RECT r;
	Vec2i dim = RenderEngine::instance().getWinSize();
	HWND wnd = GetForegroundWindow();
	GetClientRect( wnd , &r);
	POINT p = {c.x,c.y};
	ScreenToClient(wnd,&p);
	Vec2f normalized = Vec2f( (float)p.x / (float)(r.right-r.left), (float) p.y / (float) (r.bottom-r.top));
	return normalized;
}

} // End namespace